// 2013年版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package cipher

import (
	subtleoverlap "crypto/internal/subtle"
	"crypto/subtle"
	"encoding/binary"
	"errors"
)

// AEAD是一种密码模式，提供与相关
// 数据有关方法的说明，请参阅
// https:
type AEAD interface {
	// NonceSize返回必须传递给Seal的nonce的大小
	// 打开。
	NonceSize() int

	// 开销返回一个字符串长度之间的最大差值
	// 明文及其密文。
	Overhead() int

	// Seal加密和验证明文，验证
	// 附加数据，并将结果附加到dst，返回更新的
	// 片nonce的长度必须为NonceSize（）字节，并且对所有用户都是唯一的
	// 时间，对于给定的键。
	// None
	// 要将明文存储重新用于加密输出，请使用明文[：0]
	// 作为dst。否则，dst的剩余容量不得与明文重叠。
	Seal(dst, nonce, plaintext, additionalData []byte) []byte

	// Open对密文进行解密和身份验证，对
	// 附加数据，如果成功，则追加生成的纯文本
	// 到dst，返回更新的切片。nonce必须是NonceSize（）
	// 字节长，并且它和附加数据必须与
	// 传递给Seal的值。
	// None
	// 要为解密的输出重用密文存储，请使用密文[：0]
	// 作为dst。否则，dst的剩余容量不得与明文重叠。
	// None
	// 即使功能出现故障，dst的内容也会达到其容量，
	// 可能会被覆盖。
	Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error)
}

// gcmAble是由具有特定优化
// GCM的实现，如加密/aes。NewGCM将检查此接口
// 并返回特定的AEAD（如果找到）。
type gcmAble interface {
	NewGCM(nonceSize, tagSize int) (AEAD, error)
}

// None
// 标准化并使binary.BigEndian适合封送这些值
// 位以大端顺序存储。例如：
// None
// None
// None
// None
type gcmFieldElement struct {
	low, high uint64
}

// gcm表示具有特定键的伽罗瓦计数器模式。看见
// https:
type gcm struct {
	cipher    Block
	nonceSize int
	tagSize   int
	// productTable包含键H的前十六次幂。
	// 然而，它们的顺序却正好相反。请参阅NewGCMWithNonCessize。
	productTable [16]gcmFieldElement
}

// NewGCM返回给定的以Galois计数器模式包装的128位分组密码
// 使用标准的nonce长度。
// None
// 一般来说，GCM的这种实现所执行的GHASH操作不是恒定时间。
// 例外情况是，基础块是由aes.NewCipher创建的
// 在具有AES硬件支持的系统上。有关详细信息，请参阅crypto/aes软件包文档。
func NewGCM(cipher Block) (AEAD, error) {
	return newGCMWithNonceAndTagSize(cipher, gcmStandardNonceSize, gcmTagSize)
}

// NewGCMWithNonceSize返回给定的以Galois包装的128位分组密码
// 计数器模式，它接受给定长度的nonce。长度不能太长
// 是零。
// None
// 仅当需要与现有设备兼容时才使用此功能
// 使用非标准nonce长度的密码系统。所有其他用户应使用
// NewGCM，速度更快，更能防止误用。
func NewGCMWithNonceSize(cipher Block, size int) (AEAD, error) {
	return newGCMWithNonceAndTagSize(cipher, size, gcmTagSize)
}

// NewGCMWithTagSize返回以Galois包装的给定128位分组密码
// 计数器模式，该模式生成具有给定长度的标记。
// None
// 允许标记大小介于12到16字节之间。
// None
// 仅当需要与现有设备兼容时才使用此功能
// 使用非标准标记长度的密码系统。所有其他用户应使用
// NewGCM，更能抵抗误用。
func NewGCMWithTagSize(cipher Block, tagSize int) (AEAD, error) {
	return newGCMWithNonceAndTagSize(cipher, gcmStandardNonceSize, tagSize)
}

func newGCMWithNonceAndTagSize(cipher Block, nonceSize, tagSize int) (AEAD, error) {
	if tagSize < gcmMinimumTagSize || tagSize > gcmBlockSize {
		return nil, errors.New("cipher: incorrect tag size given to GCM")
	}

	if nonceSize <= 0 {
		return nil, errors.New("cipher: the nonce can't have zero length, or the security of the key will be immediately compromised")
	}

	if cipher, ok := cipher.(gcmAble); ok {
		return cipher.NewGCM(nonceSize, tagSize)
	}

	if cipher.BlockSize() != gcmBlockSize {
		return nil, errors.New("cipher: NewGCM requires 128-bit block cipher")
	}

	var key [gcmBlockSize]byte
	cipher.Encrypt(key[:], key[:])

	g := &gcm{cipher: cipher, nonceSize: nonceSize, tagSize: tagSize}

	// 我们预先计算了16倍的| key |。但是，当我们进行查找时
	// 在这个表中，我们将使用字段元素中的位
	// 因此，位的顺序将相反。通常是一个
	// 比如说，4*键应该在表的索引4中，但是由于
	// 此位排序实际上将在索引0010（基2）=2中。
	x := gcmFieldElement{
		binary.BigEndian.Uint64(key[:8]),
		binary.BigEndian.Uint64(key[8:]),
	}
	g.productTable[reverseBits(1)] = x

	for i := 2; i < 16; i += 2 {
		g.productTable[reverseBits(i)] = gcmDouble(&g.productTable[reverseBits(i/2)])
		g.productTable[reverseBits(i+1)] = gcmAdd(&g.productTable[reverseBits(i)], &x)
	}

	return g, nil
}

const (
	gcmBlockSize         = 16
	gcmTagSize           = 16
	gcmMinimumTagSize    = 12 // NIST SP 800-38D建议使用12个或更多字节的标签。
	gcmStandardNonceSize = 12
)

func (g *gcm) NonceSize() int {
	return g.nonceSize
}

func (g *gcm) Overhead() int {
	return g.tagSize
}

func (g *gcm) Seal(dst, nonce, plaintext, data []byte) []byte {
	if len(nonce) != g.nonceSize {
		panic("crypto/cipher: incorrect nonce length given to GCM")
	}
	if uint64(len(plaintext)) > ((1<<32)-2)*uint64(g.cipher.BlockSize()) {
		panic("crypto/cipher: message too large for GCM")
	}

	ret, out := sliceForAppend(dst, len(plaintext)+g.tagSize)
	if subtleoverlap.InexactOverlap(out, plaintext) {
		panic("crypto/cipher: invalid buffer overlap")
	}

	var counter, tagMask [gcmBlockSize]byte
	g.deriveCounter(&counter, nonce)

	g.cipher.Encrypt(tagMask[:], counter[:])
	gcmInc32(&counter)

	g.counterCrypt(out, plaintext, &counter)

	var tag [gcmTagSize]byte
	g.auth(tag[:], out[:len(plaintext)], data, &tagMask)
	copy(out[len(plaintext):], tag[:])

	return ret
}

var errOpen = errors.New("cipher: message authentication failed")

func (g *gcm) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
	if len(nonce) != g.nonceSize {
		panic("crypto/cipher: incorrect nonce length given to GCM")
	}
	// 健全性检查，以防止在实现失败时验证始终成功
	// 例如，未初始化标记大小。
	if g.tagSize < gcmMinimumTagSize {
		panic("crypto/cipher: incorrect GCM tag size")
	}

	if len(ciphertext) < g.tagSize {
		return nil, errOpen
	}
	if uint64(len(ciphertext)) > ((1<<32)-2)*uint64(g.cipher.BlockSize())+uint64(g.tagSize) {
		return nil, errOpen
	}

	tag := ciphertext[len(ciphertext)-g.tagSize:]
	ciphertext = ciphertext[:len(ciphertext)-g.tagSize]

	var counter, tagMask [gcmBlockSize]byte
	g.deriveCounter(&counter, nonce)

	g.cipher.Encrypt(tagMask[:], counter[:])
	gcmInc32(&counter)

	var expectedTag [gcmTagSize]byte
	g.auth(expectedTag[:], ciphertext, data, &tagMask)

	ret, out := sliceForAppend(dst, len(ciphertext))
	if subtleoverlap.InexactOverlap(out, ciphertext) {
		panic("crypto/cipher: invalid buffer overlap")
	}

	if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 {
		// AESNI代码同时解密和认证，以及
		// 因此，在标记不匹配的情况下覆盖dst。那个
		// 在这里模仿行为是为了在整个过程中保持一致
		// 平台。
		for i := range out {
			out[i] = 0
		}
		return nil, errOpen
	}

	g.counterCrypt(out, ciphertext, &counter)

	return ret, nil
}

// 反转EBITS反转i中4位数字的位顺序。
func reverseBits(i int) int {
	i = ((i << 2) & 0xc) | ((i >> 2) & 0x3)
	i = ((i << 1) & 0xa) | ((i >> 1) & 0x5)
	return i
}

// None
func gcmAdd(x, y *gcmFieldElement) gcmFieldElement {
	// 特征2字段中的加法就是XOR。
	return gcmFieldElement{x.low ^ y.low, x.high ^ y.high}
}

// None
func gcmDouble(x *gcmFieldElement) (double gcmFieldElement) {
	msbSet := x.high&1 == 1

	// 由于位顺序，加倍实际上是右移。
	double.high = x.high >> 1
	double.high |= x.low << 63
	double.low = x.low >> 1

	// 如果在移位之前设置了最高有效位，
	// 从概念上讲，成为x^128的一个术语。这比
	// 不可约多项式，因此结果必须缩减。这个
	// 不可约多项式是1+x+x^2+x^7+x^128。我们可以把它减去
	// 消除x^128处的项，这也意味着减去另一项
	// 四个任期。在特征2字段中，减法==加法==
	// 异或。
	if msbSet {
		double.low ^= 0xe100000000000000
	}

	return
}

var gcmReductionTable = []uint16{
	0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0,
	0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0,
}

// mul将y设置为y*H，其中H是GCM密钥，在NewGCMWithNonSize期间固定。
func (g *gcm) mul(y *gcmFieldElement) {
	var z gcmFieldElement

	for i := 0; i < 2; i++ {
		word := y.high
		if i == 1 {
			word = y.low
		}

		// 乘法的工作原理是将z乘以16，然后加上
		// 预计算的H的倍数之一。
		for j := 0; j < 64; j += 4 {
			msw := z.high & 0xf
			z.high >>= 4
			z.high |= z.low << 60
			z.low >>= 4
			z.low ^= uint64(gcmReductionTable[msw]) << 48

			// |表|中的值是为
			// 小端位位置。见评论
			// 在NewGCMWithNonceSize中。
			t := &g.productTable[word&0xf]

			z.low ^= t.low
			z.high ^= t.high
			word >>= 4
		}
	}

	*y = z
}

// updateBlocks根据
// 霍纳法则。块中必须有多个gcmBlockSize字节。
func (g *gcm) updateBlocks(y *gcmFieldElement, blocks []byte) {
	for len(blocks) > 0 {
		y.low ^= binary.BigEndian.Uint64(blocks)
		y.high ^= binary.BigEndian.Uint64(blocks[8:])
		g.mul(y)
		blocks = blocks[gcmBlockSize:]
	}
}

// 更新使用数据中的更多多项式项扩展y。如果数据不是
// gcmBlockSize字节长的倍数，则剩余字节为零填充。
func (g *gcm) update(y *gcmFieldElement, data []byte) {
	fullBlocks := (len(data) >> 4) << 4
	g.updateBlocks(y, data[:fullBlocks])

	if len(data) != fullBlocks {
		var partialBlock [gcmBlockSize]byte
		copy(partialBlock[:], data[fullBlocks:])
		g.updateBlocks(y, partialBlock[:])
	}
}

// gcmInc32将计数器块的最后四个字节视为大端值
// 并增加它。
func gcmInc32(counterBlock *[16]byte) {
	ctr := counterBlock[len(counterBlock)-4:]
	binary.BigEndian.PutUint32(ctr, binary.BigEndian.Uint32(ctr)+1)
}

// sliceForAppend获取一个切片和请求的字节数。它返回一个
// 切片，其中给定切片的内容后跟如此多的字节和
// 第二个片，别名为它，只包含额外的字节。如果
// 原始片具有足够的容量，则不执行分配。
func sliceForAppend(in []byte, n int) (head, tail []byte) {
	if total := len(in) + n; cap(in) >= total {
		head = in[:total]
	} else {
		head = make([]byte, total)
		copy(head, in)
	}
	tail = head[len(in):]
	return
}

// counterCrypt在计数器模式下使用g.cipher从内到外加密。
func (g *gcm) counterCrypt(out, in []byte, counter *[gcmBlockSize]byte) {
	var mask [gcmBlockSize]byte

	for len(in) >= gcmBlockSize {
		g.cipher.Encrypt(mask[:], counter[:])
		gcmInc32(counter)

		xorWords(out, in, mask[:])
		out = out[gcmBlockSize:]
		in = in[gcmBlockSize:]
	}

	if len(in) > 0 {
		g.cipher.Encrypt(mask[:], counter[:])
		gcmInc32(counter)
		xorBytes(out, in, mask[:])
	}
}

// deriveCounter根据给定的nonce计算初始GCM计数器状态。
// 见NIST SP 800-38D，第7.1节。这假定计数器中充满了
// 输入时为零。
func (g *gcm) deriveCounter(counter *[gcmBlockSize]byte, nonce []byte) {
	// 对于初始计数器，GCM有两种操作模式
	// 状态：96位（12字节）nonce的“快速路径”和“慢速路径”
	// 对于其他长度的nonce。对于96位的nonce，nonce沿
	// 使用从1开始的四字节big-endian计数器
	// 直接作为起始计数器。对于其他临时尺寸，计数器
	// 通过将其传递给GHASH函数来计算。
	if len(nonce) == gcmStandardNonceSize {
		copy(counter[:], nonce)
		counter[gcmBlockSize-1] = 1
	} else {
		var y gcmFieldElement
		g.update(&y, nonce)
		y.high ^= uint64(len(nonce)) * 8
		g.mul(&y)
		binary.BigEndian.PutUint64(counter[:8], y.low)
		binary.BigEndian.PutUint64(counter[8:], y.high)
	}
}

// auth计算GHASH（密文，附加数据），用
// 标记掩码并将结果写入输出。
func (g *gcm) auth(out, ciphertext, additionalData []byte, tagMask *[gcmTagSize]byte) {
	var y gcmFieldElement
	g.update(&y, additionalData)
	g.update(&y, ciphertext)

	y.low ^= uint64(len(additionalData)) * 8
	y.high ^= uint64(len(ciphertext)) * 8

	g.mul(&y)

	binary.BigEndian.PutUint64(out, y.low)
	binary.BigEndian.PutUint64(out[8:], y.high)

	xorWords(out, out, tagMask[:])
}
